package cn.bincker.mybatis.encrypt.entity;

import cn.bincker.mybatis.encrypt.annotation.IntegrityCheck;
import cn.bincker.mybatis.encrypt.exception.MybatisEncryptException;
import cn.bincker.mybatis.encrypt.utils.NameUtils;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import org.springframework.beans.BeanUtils;
import org.springframework.lang.Nullable;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;

@Data
public class IntegrityCheckProperty implements SecurityProperty{
    private final Class<?> type;
    private final String name;
    private final Method getter;
    private final Field field;
    private final PropertyDescriptor[] checkProperties;
    private final String[] checkColumnNames;
    public IntegrityCheckProperty(Class<?> type, String name, Method getter, Field field, IntegrityCheck integrityCheck) {
        this.type = type;
        this.name = name;
        this.getter = getter;
        this.field = field;
        var propertyMap = Arrays.stream(BeanUtils.getPropertyDescriptors(getter.getDeclaringClass()))
                .collect(Collectors.toMap(PropertyDescriptor::getName, p->p));
        checkProperties = Arrays.stream(integrityCheck.value()).map(p->{
            var property = propertyMap.get(p);
            if (property == null) throw new MybatisEncryptException("integrity check property not found: class=" + getter.getDeclaringClass() + "\tproperty=" + p);
            return property;
        }).toArray(PropertyDescriptor[]::new);
        checkColumnNames = Arrays.stream(checkProperties).map(property->getColumnName(type, property)).toArray(String[]::new);
    }

    private String getColumnName(Class<?> type, PropertyDescriptor property) {
        var method = property.getReadMethod();
        TableField tableField = null;
        if (method != null) tableField = method.getAnnotation(TableField.class);
        if (tableField != null) return tableField.value();
        Field field = getField(type, property.getName());
        if (field != null) tableField = field.getAnnotation(TableField.class);
        if (tableField != null) return tableField.value();
        return NameUtils.hump2Snake(property.getName());
    }

    @Nullable
    private static Field getField(Class<?> type, String name){
        var cls = type;
        while (cls != null && cls != Object.class){
            try {
                return cls.getDeclaredField(name);
            } catch (NoSuchFieldException ignore) {}
            cls = cls.getSuperclass();
        }
        return null;
    }

    @Override
    public Method getSetter() {
        return null;
    }
}
